import abc
from collections import OrderedDict
from enum import Enum
from typing import List, Tuple

from utils import CacheEngineKey


class PutStatus(Enum):
    LEGAL = 1
    ILLEGAL = 2


class EvictorInterface(abc.ABC):

    def update_on_hit(self, key: CacheEngineKey, cache_dict: OrderedDict) -> None:
        """
        Update the cache on hit.

        Args:
            key: The key of the cache.
            cache_dict: The cache to update.
        """
        raise NotImplementedError

    def update_on_put(
        self, cache_dict: OrderedDict, cache_size: int
    ) -> Tuple[PutStatus, List[CacheEngineKey]]:
        """
        Update the cache on put.

        Args:
            cache_dict: The cache to update.
            cache_size: The requested cache size.

        Returns:
            A tuple of (put_status, keys_to_evict).
            put_status: The status of the put.
            keys_to_evict: The keys to evict.
        """
        raise NotImplementedError


class LRUEvictor(EvictorInterface):

    def __init__(self, max_size: float = 10.0):
        self.max_size = int(max_size * 1024**3)
        self.current_size = 0

    def update_on_hit(self, key: CacheEngineKey, cache_dict: OrderedDict) -> None:
        """
        Update the cache on hit.
        """
        cache_dict.move_to_end(key)

    def update_on_put(
        self, cache_dict: OrderedDict, cache_size: int
    ) -> Tuple[PutStatus, List[CacheEngineKey]]:
        """
        Update the cache on put.
        """
        keys_to_evict = []

        if cache_size > self.max_size:
            return PutStatus.ILLEGAL, keys_to_evict

        obj_iter = iter(cache_dict)
        while cache_size + self.current_size > self.max_size:
            evict_key = next(obj_iter)
            evict_metadata = cache_dict[evict_key]
            if evict_metadata.is_pinned:
                continue
            keys_to_evict.append(evict_key)
            self.current_size -= evict_metadata.size

        self.current_size += cache_size
        return PutStatus.LEGAL, keys_to_evict
